package com.cardone.common.security.shiro.web.filter;

import com.cardone.common.cache.util.CacheUtils;
import com.cardone.common.util.MapperUtils;
import com.cardone.common.util.ReturnDataUtils;
import com.cardone.context.Attributes;
import com.cardone.context.CodeException;
import com.google.common.collect.Lists;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Created by c100000002 on 15-2-5.
 */
@Slf4j
public class KickoutSessionControlFilter extends PathMatchingFilter {
    @Setter
    private String kickoutUrl = "/kickoutInfo.html"; //踢出后到的地址

    @Setter
    private String kickoutMessage = "您的帐号在另一处已登录!"; //踢出后到的地址

    @Setter
    private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户

    @Setter
    private int maxSession = 1; //同一个帐号最大会话数 默认1

    private String kickoutParam = "kickout";//踢出参数

    private String cacheParam = "shiro-kickout-session";//踢出缓存参数

    /**
     * json路径集合
     */
    @Setter
    private List<String> jsonUrlList = Lists.newArrayList("/**/*.json", "/**/*.txt");

    private List<String> usernameParams = Lists.newArrayList(Attributes.id.name(), Attributes.code.name(), Attributes.username.name());

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)throws Exception {
        Subject subject = SecurityUtils.getSubject();

        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果没有登录，直接进行之后的流程
            return true;
        }

        String username = null;

        for (String usernameParam : usernameParams) {
            if (StringUtils.isBlank(username)) {
                try {
                    username = BeanUtils.getProperty(subject.getPrincipal(), usernameParam);
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }

        if (StringUtils.isBlank(username)) {
            return true;
        }

        Deque<Serializable> deque = CacheUtils.get(cacheParam, username, () -> new LinkedList<>());

        Session session = subject.getSession();

        Serializable sessionId = session.getId();

        //如果队列里没有此sessionId，且用户没有被踢出；放入队列
        if (!deque.contains(sessionId) && session.getAttribute(kickoutParam) == null) {
            deque.push(sessionId);

            CacheUtils.put(cacheParam, username, deque);
        }

        //如果队列里的sessionId数超出最大会话数，开始踢人
        while (deque.size() > maxSession) {
            Serializable kickoutSessionId;

            if (kickoutAfter) { //如果踢出后者
                kickoutSessionId = deque.removeFirst();
            } else { //否则踢出前者
                kickoutSessionId = deque.removeLast();
            }

            try {
                Session kickoutSession = SecurityUtils.getSecurityManager().getSession(new DefaultSessionKey(kickoutSessionId));

                if (kickoutSession != null) {
                    //设置会话的kickout属性表示踢出了
                    kickoutSession.setAttribute(kickoutParam, true);
                }
            } catch (Exception e) {//ignore exception
                log.error(e.getMessage(), e);
            }

            CacheUtils.put(cacheParam, username, deque);
        }

        if (session.getAttribute(kickoutParam) == null) {
            return true;
        }

        try {
            subject.logout(); //会话被踢出了
        } catch (Exception e) { //ignore
            log.error(e.getMessage(), e);
        }

        String servletPath = com.cardone.common.util.WebUtils.getServletPath();

        if (!com.cardone.common.util.StringUtils.matchList(jsonUrlList, servletPath)) {
            WebUtils.saveRequest(request);

            WebUtils.issueRedirect(request, response, kickoutUrl);

            return false;
        }

        response.setCharacterEncoding(CharEncoding.UTF_8);
        response.setContentType("application/json");

        final Map<String, String> returnDataMap = ReturnDataUtils.newErrorMap(new CodeException(kickoutMessage));

        try (Writer out = response.getWriter()) {
            out.write(MapperUtils.toJson(returnDataMap));
        }

        return false;
    }
}
